import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

/**
 * DESC:
 *
 * @author : baicaixiaozhan
 * @since : 2020-12-19
 */
public final class AESGCMUtils {
    public static final int AES_KEY_SIZE = 256;
    public static final int GCM_iv_LENGTH = 12;
    public static final int GCM_TAG_LENGTH = 16;

    private AESGCMUtils() {
        throw new AssertionError();
    }

    public static byte[] initVector() {
        byte[] iv = new byte[GCM_iv_LENGTH];
        SecureRandom random = new SecureRandom();
        random.nextBytes(iv);
        return iv;
    }

    public static SecretKey keyGenerator() {
        KeyGenerator keyGenerator = null;
        SecretKey key = null;
        try {
            keyGenerator = KeyGenerator.getInstance("AES");
            keyGenerator.init(AES_KEY_SIZE);
            key = keyGenerator.generateKey();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return key;
    }

    public static byte[] encryptText(byte[] plaintext, SecretKey key, byte[] iv) {
        byte[] bytes = null;
        try {
            // Get Cipher Instance
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            // Create SecretKeySpec
            SecretKeySpec keySpec = new SecretKeySpec(key.getEncoded(), "AES");
            // Create GCMParameterSpec
            GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, iv);
            // Initialize Cipher for ENCRYPT_MODE
            cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmParameterSpec);
            // Perform Encryption
            bytes = cipher.doFinal(plaintext);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return bytes;
    }

    public static byte[] decryptText(byte[] cipherText, SecretKey key, byte[] iv) {
        byte[] decryptedText = null;
        try {
            // Get Cipher Instance
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");

            // Create SecretKeySpec
            SecretKeySpec keySpec = new SecretKeySpec(key.getEncoded(), "AES");

            // Create GCMParameterSpec
            GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, iv);

            // Initialize Cipher for DECRYPT_MODE
            cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmParameterSpec);

            // Perform Decryption
            decryptedText = cipher.doFinal(cipherText);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return decryptedText;
    }

    /**
     * Reads the file in the given path into a byte array
     *
     * @param file: Path to the file, including the file name. For example: "C:/myfolder/myfile.txt"
     * @return byte array of the file data
     */
    public static byte[] readFile(File file) {

        byte[] fileData = new byte[(int) file.length()];

        try (FileInputStream fileInputStream = new FileInputStream(file)) {
            fileInputStream.read(fileData);
        } catch (IOException e) {
            e.printStackTrace();
        }

        return fileData;
    }

    /**
     * Writes a file with the given data into a file with the given path
     *
     * @param path: Path to the file to be created, including the file name. For example: "C:/myfolder/myfile.txt"
     * @param data: byte array of the data to be written
     */
    public static void writeFile(String path, byte[] data) {
        if (data == null) {
            return;
        }
        try (FileOutputStream fileOutputStream = new FileOutputStream(path)) {
            fileOutputStream.write(data);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 写出 加密文件
     *
     * @param file
     */
    public static void writeFileInEncrypt(File file) {
        byte[] fileBytes = readFile(file);
        SecretKey key = AESGCMUtils.keyGenerator();
        byte[] iv = AESGCMUtils.initVector();
        byte[] resultBytes = encryptText(fileBytes, key, iv);

        writeFile(file.getPath(), resultBytes);
    }
    public static void writeFileInDecrypt(File file) {
        byte[] fileBytes = readFile(file);
        SecretKey key = AESGCMUtils.keyGenerator();
        byte[] iv = AESGCMUtils.initVector();
        byte[] resultBytes = decryptText(fileBytes, key, iv);

        writeFile(file.getPath(), resultBytes);
    }

}
